<?php
/**************************************************************************
 *
 *   Copyright 2010 American Public Media Group
 *
 *   This file is part of AIR2.
 *
 *   AIR2 is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   AIR2 is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with AIR2.  If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************/

/**
 * User
 *
 * AIR2 User account
 *
 * @property integer $user_id
 * @property string $user_uuid
 * @property string $user_username
 * @property string $user_password
 * @property timestamp $user_pswd_dtim
 * @property string $user_first_name
 * @property string $user_last_name
 * @property string $user_pref
 * @property string $user_type
 * @property string $user_status
 * @property integer $user_cre_user
 * @property integer $user_upd_user
 * @property timestamp $user_cre_dtim
 * @property timestamp $user_upd_dtim
 * @property Doctrine_Collection $Batch
 * @property Doctrine_Collection $Project
 * @property Doctrine_Collection $UserEmailAddress
 * @property Doctrine_Collection $UserOrg
 * @property Doctrine_Collection $UserPhoneNumber
 * @property Doctrine_Collection $UserUri
 * @author rcavis
 * @package default
 */
class User extends AIR2_Record {
    /* code_master values */
    public static $STATUS_ACTIVE = 'A';
    public static $STATUS_INACTIVE = 'F';
    public static $TYPE_AIR_USER = 'A';
    public static $TYPE_JOURNALIST = 'J';
    public static $TYPE_RELATED = 'R';
    public static $TYPE_SYSTEM = 'S';

/* special case to allow a MANAGER to write changes (e.g. to a child) */
    public static $ALLOW_MANAGER_TO_WRITE = false;

    private $authz = false;
    private $authz_uuids = false;

    /**
     * Set the table columns
     */
    public function setTableDefinition() {
        $this->setTableName('user');
        $this->hasColumn('user_id', 'integer', 4, array(
                'primary' => true,
                'autoincrement' => true,
            ));
        $this->hasColumn('user_uuid', 'string', 12, array(
                'fixed' => true,
                'notnull' => true,
                'unique' => true,
            ));
        $this->hasColumn('user_username', 'string', 255, array(
                'unique' => true,
            ));
        $this->hasColumn('user_password', 'string', 32, array(
                'fixed' => true,
            ));
        $this->hasColumn('user_pswd_dtim', 'timestamp', null, array(

            ));
        $this->hasColumn('user_first_name', 'string', 64, array(
                'notnull' => true,
            ));
        $this->hasColumn('user_last_name', 'string', 64, array(
                'notnull' => true,
            ));
        $this->hasColumn('user_pref', 'string', null, array(

            ));
        $this->hasColumn('user_type', 'string', 1, array(
                'fixed' => true,
                'notnull' => true,
                'default' => self::$TYPE_AIR_USER,
            ));
        $this->hasColumn('user_status', 'string', 1, array(
                'fixed' => true,
                'notnull' => true,
                'default' => self::$STATUS_ACTIVE,
            ));
        $this->hasColumn('user_cre_user', 'integer', 4, array(

            ));
        $this->hasColumn('user_upd_user', 'integer', 4, array(

            ));
        $this->hasColumn('user_cre_dtim', 'timestamp', null, array(
                'notnull' => true,
            ));
        $this->hasColumn('user_upd_dtim', 'timestamp', null, array(

            ));

        parent::setTableDefinition();
    }


    /**
     * Set table relations
     */
    public function setUp() {
        parent::setUp();
        $this->hasMany('Batch', array(
                'local' => 'user_id',
                'foreign' => 'batch_user_id'
            ));
        $this->hasMany('Project', array(
                'local' => 'user_id',
                'foreign' => 'prj_user_id'
            ));
        $this->hasMany('UserEmailAddress', array(
                'local' => 'user_id',
                'foreign' => 'uem_user_id'
            ));
        $this->hasMany('UserOrg', array(
                'local' => 'user_id',
                'foreign' => 'uo_user_id'
            ));
        $this->hasMany('UserPhoneNumber', array(
                'local' => 'user_id',
                'foreign' => 'uph_user_id'
            ));
        $this->hasMany('UserUri', array(
                'local' => 'user_id',
                'foreign' => 'uuri_user_id'
            ));
        $this->hasMany('SavedSearch', array(
                'local' => 'user_id',
                'foreign' => 'ssearch_cre_user',
            )
        );

        // add a mutator on the password field
        $this->hasMutator('user_password', '_encrypt_password');
    }


    /**
     *
     *
     * @return unknown
     */
    public function is_system() {
        return $this->user_type === self::$TYPE_SYSTEM;
    }


    /**
     *
     *
     * @param unknown $event
     * @return unknown
     */
    public function preDelete($event) {
        // do not orphan any project_org records, nor hit FK constraint.
        $conn = AIR2_DBManager::get_master_connection();
        $sql  = "UPDATE project_org SET porg_contact_user_id=1 WHERE porg_contact_user_id=".$this->user_id;
        return $conn->execute($sql);
    }


    /**
     * Mutates the user_password to be encrypted
     *
     * @param string  $cleartext
     */
    protected function _encrypt_password($cleartext) {
        $encrypted = $this->_encrypt_string($cleartext);
        $this->_set('user_password', $encrypted);
    }


    /**
     * Encrypts a string
     *
     * @param string  $str
     * @return string the encrypted string
     */
    protected function _encrypt_string($str) {
        $salt = md5($str);
        return md5($str . $salt);
    }


    /**
     * Checks for equality between a password string against user_password
     *
     * @param string  $cleartext
     * @return boolean
     */
    public function check_password($cleartext) {
        $encrypted = $this->_encrypt_string($cleartext);
        return $encrypted === $this->user_password;
    }


    /**
     * Custom AIR2 validation before update/save
     *
     * @param unknown $q      (reference)
     * @param unknown $alias
     * @param unknown $search
     * @param unknown $useOr  (optional)
     */
    public static function add_search_str(&$q, $alias, $search, $useOr=null) {
        $a = ($alias) ? "$alias." : "";
        $str = "(".$a."user_first_name LIKE ? OR ".$a."user_last_name LIKE ? OR ".
            $a."user_username like ?)";

        if ($useOr) {
            $q->orWhere($str, array("$search%", "$search%", "$search%"));
        }
        else {
            $q->addWhere($str, array("$search%", "$search%", "$search%"));
        }
    }


    /**
     * Get authorization object for this user. Array of org_id => bitmask (role).
     *
     * @return array
     */
    public function get_authz() {
        if ($this->authz) {
            return $this->authz;
        }
        $authz = array();
        foreach ($this->UserOrg as $usr_org) {
            if ($usr_org->uo_status == UserOrg::$STATUS_ACTIVE) {
                $this->_add_auth_org(
                    $authz,
                    $usr_org->Organization,
                    $usr_org->AdminRole->ar_code
                );
            }
        }
        $this->authz = $authz;
        return $authz;
    }


    /**
     * Like get_authz() but returns org_uuid as key instead of org_id.
     *
     * @return array $authz_uuids
     */
    public function get_authz_uuids() {
        if ($this->authz_uuids) {
            return $this->authz_uuids;
        }
        $authz = $this->get_authz();
        $org_ids = array_keys($authz);
        $q = AIR2_Query::create();
        $q->select('o.org_uuid,o.org_id');
        $q->from('Organization o');
        $q->whereIn('o.org_id', $org_ids);
        $org_uuids = $q->execute();
        $authz_uuids = array();
        $id2uuid = array();
        foreach ($org_uuids as $o) {
            $id2uuid[$o->org_id] = $o->org_uuid;
        }
        foreach ($authz as $org_id=>$role) {
            $authz_uuids[$id2uuid[$org_id]] = $role;
        }
        $this->authz_uuids = $authz_uuids;
        return $authz_uuids;
    }


    /**
     * Returns a SQL/DQL-ready string for $column using
     * the org_ids returned by get_authz() and using a bitwise
     * comparison against $role.
     *
     * @param integer $role
     * @param string  $column
     * @return unknown
     */
    public function get_authz_str($role, $column) {
        $authz = $this->get_authz();
        $org_ids = array();
        foreach ($authz as $org_id => $mask) {
            if ($mask & $role) {
                $org_ids[] = $org_id;
            }
        }
        $org_str = "$column IS NULL";
        if (count($org_ids)) {
            $org_str = "$column IN (" . implode(",", $org_ids) . ")";
        }
        return $org_str;
    }



    /**
     * Helper function to recursively add org-roles to an authorization object.
     * The direct flag indicates whether a role is direct (set by a UserOrg
     * object), or inherited as the child of another organization.
     *
     * @param array   $authz   (reference)
     * @param Doctrine_Record $org_rec
     * @param string  $ar_code
     * @param boolean $direct  (optional)
     */
    private function _add_auth_org(&$authz, $org_rec, $ar_code, $direct=true) {
        // set bitmask from ar_code
        $bitmask = 0;
        switch ($ar_code) {
        case 'R':
            $bitmask = AIR2_AUTHZ_READER;
            break;
        case 'W':
            $bitmask = AIR2_AUTHZ_READER | AIR2_AUTHZ_WRITER;
            break;
        case 'M';
            $bitmask = AIR2_AUTHZ_READER | AIR2_AUTHZ_WRITER | AIR2_AUTHZ_MANAGER;
            break;
        }

        // add org-role, if it's direct or not already set
        if ($direct || !array_key_exists($org_rec->org_id, $authz)) {
            $authz[$org_rec->org_id] = $bitmask;
        }

        // if there are any child orgs, add this permission to them
        foreach ($org_rec->children as $child_org) {

            // in theory this should never happen, but if it did,
            // this would be an endless loop
            if ($child_org->org_id == $org_rec->org_id) {
                continue;
            }

            // INDIRECT, so any explicit org-role will override inherited ones
            $this->_add_auth_org($authz, $child_org, $ar_code, false);
        }

    }


    /**
     * Everyone may read the user.
     *
     * @param User    $user
     * @return authz integer
     */
    public function user_may_read($user) {
        // wide open
        return AIR2_AUTHZ_IS_PUBLIC;
    }


    /**
     * Only owner and system users may modify the record.
     *
     * @param User    $user
     * @return authz integer
     */
    public function user_may_write($user) {
        if ($user->is_system()) {
            return AIR2_AUTHZ_IS_SYSTEM;
        }

        // system users and owner only, unless...
        if ($user->user_uuid == $this->user_uuid) {
            return AIR2_AUTHZ_IS_OWNER;
        }

        // ... class flag set 
        // (as when editing a child UserOrg where $user is a MANAGER of the org)
        $authz = $user->get_authz();
        foreach ($authz as $org_id => $role) {
            if ($role & AIR2_AUTHZ_MANAGER && self::$ALLOW_MANAGER_TO_WRITE) {
                return AIR2_AUTHZ_IS_MANAGER;
            }       
        }

        // TODO other cases? manager of home org e.g.?
        return AIR2_AUTHZ_IS_DENIED;
    }


    /**
     *
     *
     * @param User    $user
     * @return authz integer
     */
    public function user_may_manage($user) {
        return $this->user_may_write($user);
    }


    /**
     * Get a User preference value.
     *
     * @param string  $name
     * @return string
     */
    public function get_pref($name) {
        $prefs = json_decode($this->user_pref, true);
        return isset($prefs[$name]) ? $prefs[$name] : null;
    }


    /**
     * Set a User preference value.
     *
     * @param string  $name
     * @param string  $value
     */
    public function set_pref($name, $value) {
        $prefs = json_decode($this->user_pref, true);
        if (!$prefs) {
            $prefs = array(); //initial setup
        }
        $prefs[$name] = $value;
        $this->user_pref = json_encode($prefs);
    }


}
